using System;
using System.Text;

namespace TestCases.Utils.Formatters
{
    /// <summary>
    /// Reusable and thread-safe formatter.
    /// Probably need to be rewritten in c++ for performance reason... 
    /// StringBuilder.Append is expensive
    /// </summary>
    public static class StringFormatter
    {

        public const char CloseBrace = '}';
        public const char OpenBrace = '{';

        public static string FormatValue(string format, Func<string, string> resolveFunc)
        {
            if (format.Length > 0)
            {
                
                var resultBuilder = new StringBuilder(format.Length + (format.Length/2));
                var variableBuilder = new StringBuilder(format.Length/4);               
                
                int openBracePos = -1;                
                BraceType previousBrace = BraceType.NotBrace;
                bool parsingVariable = false;
                
                for (int i=0; i<format.Length; i++)
                {
                                                        
                    char current = format[i];
                    BraceType currentBrace = ToBraceType(current);
                    int prevPos = i - 1;

                    
                    if (BraceType.NotBrace == previousBrace)
                    {                                                
                        if (BraceType.NotBrace == currentBrace)
                        {
                            ((parsingVariable) ? variableBuilder : resultBuilder).Append(current);
                        }
                        previousBrace = currentBrace;
                        continue;
                    }

                    if (previousBrace == currentBrace)
                    {
                        ((parsingVariable) ? variableBuilder : resultBuilder).Append(current);
                        previousBrace = BraceType.NotBrace;
                        continue;
                    }

                    if (BraceType.OpenBrace == previousBrace)
                    {
                                                
                        if (parsingVariable)
                        {
                            throw BraceAlreadyOpened(prevPos, openBracePos);
                        }

                        openBracePos = prevPos;
                        parsingVariable = true;
                        if (current != CloseBrace)
                        {
                            variableBuilder.Append(current);
                        }
                        
                    }
                    else /*if (BraceType.CloseBrace == previousBrace)*/
                    {
                        
                        if (!parsingVariable)
                        {
                            throw BraceNotOpened(prevPos);
                        }
                        parsingVariable = false;
                        resultBuilder.Append(resolveFunc(variableBuilder.ToString())).Append(current);
                        variableBuilder.Length = 0;
                        
                    }
                    previousBrace = currentBrace;
                }
                
                int lastPosition = format.Length - 1;
                if (openBracePos < 0)
                {
                    openBracePos = lastPosition;
                }
                
                
                switch (previousBrace)
                {
                    case BraceType.OpenBrace:
                        if (parsingVariable)
                        {
                            throw BraceAlreadyOpened(lastPosition, openBracePos);
                        }
                        throw BraceNotClosed(openBracePos);

                    case BraceType.CloseBrace:
                        if (!parsingVariable)
                        {
                            throw BraceNotOpened(lastPosition);
                        }
                        string varName = variableBuilder.ToString();
                        variableBuilder.Length = 0;
                        resultBuilder.Append(resolveFunc(varName));
                        break;

                    default:
                        if (parsingVariable)
                        {
                            throw BraceNotClosed(openBracePos);
                        }
                        break;
                }

                return resultBuilder.ToString();
                
            }
            return format;
        }

        private static BraceType ToBraceType(char c)
        {
            if (c == OpenBrace)
            {
                return BraceType.OpenBrace;
            }
            if (c == CloseBrace)
            {
                return BraceType.CloseBrace;
            }
            return BraceType.NotBrace;
        }

        private static VariableFormatException BraceAlreadyOpened(int position, int openBracePosition)
        {
            return new VariableFormatException(
                string.Format("Brace already opened at {0} position", openBracePosition), position);
        }

        private static VariableFormatException BraceNotOpened(int position)
        {
            return new VariableFormatException("Brace not opened, but close brace appears", position);
        }

        private static VariableFormatException BraceNotClosed(int position)
        {
            return new VariableFormatException(
                string.Format("Brace opened at {0} position was not closed", position), position);
        }

        private enum BraceType : byte
        {
            OpenBrace,
            CloseBrace,
            NotBrace
        }
    }
}